package com.zkthink.ceres.promotion.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.zkthink.base.R;
import com.zkthink.base.service.SuperServiceImpl;
import com.zkthink.cache.repository.RedisRepositoryImpl;
import com.zkthink.ceres.pay.service.PayService;
import com.zkthink.ceres.promotion.constants.PromotionConstant;
import com.zkthink.ceres.promotion.dao.CouponMapper;
import com.zkthink.ceres.promotion.dao.PromotionApplyInfoMapper;
import com.zkthink.ceres.promotion.dao.PromotionMapper;
import com.zkthink.ceres.promotion.dao.PromotionProductMapper;
import com.zkthink.ceres.promotion.dto.PromotionAdminDTO;
import com.zkthink.ceres.promotion.entity.Coupon;
import com.zkthink.ceres.promotion.entity.Promotion;
import com.zkthink.ceres.promotion.entity.PromotionApplyInfo;
import com.zkthink.ceres.promotion.entity.TenantPromotionApplyInfo;
import com.zkthink.ceres.promotion.enums.CouponBizTypeEnum;
import com.zkthink.ceres.promotion.enums.PromotionAuditStatusEnum;
import com.zkthink.ceres.promotion.enums.PromotionStatusEnum;
import com.zkthink.ceres.promotion.enums.PromotionTypeEnum;
import com.zkthink.ceres.promotion.service.PromotionAdminService;
import com.zkthink.ceres.promotion.vo.admin.PromotionAdminPageVO;
import com.zkthink.ceres.promotion.vo.admin.PromotionAdminVO;
import com.zkthink.database.mybatis.conditions.Wraps;
import com.zkthink.dozer.DozerUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

/**
 * <p>
 * 业务实现类 营销活动后台接口
 * </p>
 *
 * @author kellen
 * @date 2020-06-16
 */
@Slf4j
@Service
@AllArgsConstructor
public class PromotionAdminServiceImpl extends
    SuperServiceImpl<PromotionMapper, Promotion> implements PromotionAdminService {

  private DozerUtils dozerUtils;

  private CouponMapper couponMapper;

  private PromotionApplyInfoMapper promotionApplyInfoMapper;

  private PromotionProductMapper promotionProductMapper;

  private RedisRepositoryImpl redisRepository;

  private PayService payService;

  @Override
  public R<Boolean> addPromotion(Promotion promotion) {
    baseMapper.insert(promotion);
    return R.success();
  }

  @Override
  public R<PromotionAdminPageVO> findAdminPromotionList(PromotionAdminDTO req) {
    PromotionAdminPageVO pageVO = null;
    if (req == null) {
      return R.fail("-1", "参数异常");
    }
    if (req.getPageIndex() == null) {
      req.setPageIndex(1);
    }
    if (req.getPageSize() == null) {
      req.setPageSize(10);
    }
    long total = 0L;
    List<PromotionAdminVO> voList = null;
    switch (req.getType()) {
      case 1:
        Page<Promotion> promotionPage = new Page<>(req.getPageIndex(), req.getPageSize());
        QueryWrapper<Promotion> wrapper = new QueryWrapper<>();
        wrapper.eq("is_delete", 0);
        if (StringUtils.isNoneBlank(req.getName())) {
          wrapper.like("name", req.getName());
        }
        if (req.getPromotionType() != null) {
          wrapper.eq("promotion_type", req.getPromotionType());
        }
        wrapper.orderByDesc("update_time");

        int status = req.getPromotionStatus();
        PromotionStatusEnum statusEnum = PromotionStatusEnum.getByCode(status);
        switch (statusEnum) {
          case APPLY_NOT_STATED://报名未开始
            wrapper.gt("apply_start_time", LocalDateTime.now());
            break;
          case APPLY_ING://报名进行中
            wrapper.lt("apply_start_time", LocalDateTime.now());
            wrapper.gt("apply_end_time", LocalDateTime.now());
            break;
          case ACTIVITY_NOT_STATED://活动未开始
            wrapper.lt("apply_end_time", LocalDateTime.now());
            wrapper.gt("activity_start_time", LocalDateTime.now());
            break;
          case ACTIVITY_ING://活动进行中
            wrapper.lt("activity_start_time", LocalDateTime.now());
            wrapper.gt("activity_end_time", LocalDateTime.now());
            break;
          case ACTIVITY_CLOSED://活动已结束
            wrapper.lt("activity_end_time", LocalDateTime.now());
            break;
          default:
            break;
        }

        int auditStatus = req.getAuditStatus();
        PromotionAuditStatusEnum auditStatusEnum = PromotionAuditStatusEnum.getByCode(auditStatus);
        switch (auditStatusEnum) {
          case AUDITING:
          case APPLY_SUCCESS:
          case APPLY_FAIL:
            total = baseMapper
                .findPromotionListWithApplyInfoCount(req.getName(), req.getPromotionStatus(),
                    req.getPromotionType(), req.getAuditStatus());
            voList = baseMapper
                .findPromotionListWithApplyInfo(req.getName(), req.getPromotionStatus(),
                    req.getPromotionType(), req.getAuditStatus(),
                    (req.getPageIndex() - 1) * req.getPageSize(), req.getPageSize());
            break;
          default:
            IPage<Promotion> pageResult = baseMapper.selectPage(promotionPage, wrapper);
            total = pageResult.getTotal();
            voList = dozerUtils.mapList(pageResult.getRecords(), PromotionAdminVO.class);
            List<Long> promotionIdList = Lists.newArrayList();
            for (PromotionAdminVO adminVO : voList) {
              promotionIdList.add(adminVO.getId());
            }
            if (promotionIdList.size() > 0) {
              List<PromotionApplyInfo> promotionApplyInfoList = promotionApplyInfoMapper
                  .selectList(Wraps.<PromotionApplyInfo>lbQ()
                      .eq(PromotionApplyInfo::getIsDelete, 0)
                      .in(PromotionApplyInfo::getPromotionId, promotionIdList));
              if (promotionApplyInfoList.size() > 0) {
                Map<Long, PromotionApplyInfo> applyInfoMap = new HashMap<Long, PromotionApplyInfo>();
                for (PromotionApplyInfo applyInfo : promotionApplyInfoList) {
                  applyInfoMap.put(applyInfo.getPromotionId(), applyInfo);
                }
                for (PromotionAdminVO vo : voList) {
                  PromotionApplyInfo applyInfo = applyInfoMap.get(vo.getId());
                  if (applyInfo != null) {
                    vo.setApplyId(applyInfo.getId());
                    vo.setApplyStatus(applyInfo.getStatus());
                    vo.setProductIds(applyInfo.getProductIds());
                  }
                }
              }
            }
            break;
        }
        break;
      case 2:
        total = baseMapper
            .findPromotionListWithApplyInfoCount(req.getName(), req.getPromotionStatus(),
                req.getPromotionType(), req.getAuditStatus());
        voList = baseMapper.findPromotionListWithApplyInfo(req.getName(), req.getPromotionStatus(),
            req.getPromotionType(), req.getAuditStatus(),
            (req.getPageIndex() - 1) * req.getPageSize(), req.getPageSize());
        break;
      default:
        break;
    }

    //查询活动对应的商品数量和商家数量 平台端才需要查询
    if (req.getType() == 1) {
      for (PromotionAdminVO adminVO : voList) {
        if (adminVO.getStatus() == PromotionStatusEnum.APPLY_NOT_STATED.getCode()) {
          adminVO.setTenantCount(0);
          adminVO.setProductCount(0);
        } else {
          List<PromotionApplyInfo> applyInfoList = promotionApplyInfoMapper
              .selectListIgnoreTenant(adminVO.getId(), req.getAuditStatus());
          adminVO.setTenantCount(applyInfoList.size());
          Integer productCount = 0;
          for (PromotionApplyInfo applyInfo : applyInfoList) {
            productCount += applyInfo.getProductIds().split(",").length;
          }
          adminVO.setProductCount(productCount);
        }
      }
    }

    pageVO = new PromotionAdminPageVO();
    pageVO.setTotal(total);
    pageVO.setRecords(voList);
    return R.success(pageVO);
  }

  @Override
  public R<Boolean> updatePromotionStatus() {

    //到了报名开始时间，但是还没有改状态的
    baseMapper.updateStatusToApplyStart();

    //到了报名结束时间，但是还没有改状态的
    baseMapper.updateStatusToApplyEnd();

    //到了活动开始时间，但是还没有改状态的
    QueryWrapper<Promotion> wrapper = new QueryWrapper<>();
    wrapper.eq("is_delete", 0);
    wrapper.in("status", PromotionStatusEnum.APPLY_NOT_STATED.getCode(),
        PromotionStatusEnum.APPLY_ING.getCode(), PromotionStatusEnum.ACTIVITY_NOT_STATED.getCode());
    wrapper.lt("activity_start_time", LocalDateTime.now());
    wrapper.gt("activity_end_time", LocalDateTime.now());
    List<Promotion> promotionList = baseMapper.selectList(wrapper);
    List<Long> promotionIdList = promotionList.stream().map(Promotion::getId)
        .collect(Collectors.toList());
    //活动变成开始之后，查找那些报名信息，将活动和商品关联
    if (promotionIdList.size() > 0) {

      //如果是营销方式是优惠券，解析出优惠券，新增到数据库
      for(Promotion promotion:promotionList){
        if(promotion.getPromotionType()==PromotionTypeEnum.DISCOUNT_COUPON.getCode()){
          List<Map<String, Object>> promotionDetail = promotion.getPromotionDetail();
          if (promotionDetail != null) {
            for (Map<String, Object> map : promotionDetail) {
              Coupon coupon = Coupon.builder().bizType(CouponBizTypeEnum.PROMOTION.getCode())
                      .fullAmount(Long.valueOf(map.get("fullAmount").toString()))
                      .discountAmount(Long.valueOf(map.get("discountAmount").toString()))
                      .bizId(promotion.getId().toString())
                      .publishStartTime(promotion.getActivityStartTime())
                      .publishEndTime(promotion.getActivityEndTime())
                      .useStartTime(promotion.getActivityStartTime())
                      .useEndTime(promotion.getActivityEndTime()).build();
              couponMapper.insert(coupon);
            }
          }
        }
      }


      Promotion updatePromotion = new Promotion();
      updatePromotion.setStatus(PromotionStatusEnum.ACTIVITY_ING.getCode());
      baseMapper
          .update(updatePromotion, Wraps.<Promotion>lbQ().in(Promotion::getId, promotionIdList));

      Map<Long, Long> promotionIdStartTimeMap = new HashMap<>();
      for (Promotion promotion : promotionList) {
        promotionIdStartTimeMap.put(promotion.getId(),
            promotion.getActivityStartTime().toInstant(ZoneOffset.of("+8")).toEpochMilli());
      }

      QueryWrapper<PromotionApplyInfo> applyInfoQueryWrapper = new QueryWrapper<>();
      applyInfoQueryWrapper.in("promotion_id", promotionIdList);
      applyInfoQueryWrapper.eq("status", PromotionAuditStatusEnum.APPLY_SUCCESS.getCode());
      applyInfoQueryWrapper.eq("is_delete", 0);
      List<PromotionApplyInfo> promotionApplyInfoList = promotionApplyInfoMapper
          .selectList(applyInfoQueryWrapper);
      if (promotionApplyInfoList.size() > 0) {
        Map<String, Set<Long>> productIdPromotionIdSetMap = new HashMap<>();
        for (PromotionApplyInfo applyInfo : promotionApplyInfoList) {
          String[] productIds = applyInfo.getProductIds().split(",");
          for (String productId : productIds) {
            if (productIdPromotionIdSetMap.get(productId) == null) {
              productIdPromotionIdSetMap.put(productId, new HashSet<Long>());
            }
            productIdPromotionIdSetMap.get(productId).add(applyInfo.getPromotionId());
            promotionProductMapper.insert(applyInfo.getPromotionId(),Long.valueOf(productId));
          }
        }
        for (Map.Entry<String, Set<Long>> entry : productIdPromotionIdSetMap.entrySet()) {
          String key = PromotionConstant.PRODUCT_PROMOTION_RELATION_PREFIX + entry.getKey();
          Set<Long> promotionIdSet = entry.getValue();
          for (Long promotionId : promotionIdSet) {
            redisRepository.getRedisTemplate().opsForZSet().add(key, promotionId.toString(),
                promotionIdStartTimeMap.get(promotionId).doubleValue());
          }
        }
      }

      //将活动信息存储到缓存中
      for (Promotion promotion : promotionList) {
        redisRepository.set(PromotionConstant.PROMOTION_PREFIX + promotion.getId(),
            JSON.toJSONString(promotion));
      }

      //活动开始之后，还没有审核，或者审核失败的，都要退保证金
      List<Long> needSecurityList = new ArrayList<Long>();
      Map<Long, Long> depositMap = new HashMap<Long,Long>();
      for(Promotion promotion:promotionList){
        if(promotion.getIsNeedSecurityDeposit()==1){
          needSecurityList.add(promotion.getId());
          depositMap.put(promotion.getId(),promotion.getSecurityDeposit());
        }
      }

      if(needSecurityList.size()>0){
        List<TenantPromotionApplyInfo> applyInfoList = promotionApplyInfoMapper.selectListForRefundDepositWithTenant(needSecurityList);

        if(applyInfoList.size()>0){
          for(TenantPromotionApplyInfo applyInfo:applyInfoList){
            try{
              String tenantCode = applyInfo.getTenantCode();
              String outTradeNo = applyInfo.getPayDepositOrderId();
              Long applyInfoId = applyInfo.getId();
              Long promotionId = applyInfo.getPromotionId();

              String outRefundNo = DigestUtils.md5DigestAsHex((applyInfoId+"_"+promotionId+"_"+tenantCode).getBytes());

              WxPayRefundRequest refundRequest = new WxPayRefundRequest();
              refundRequest.setOutTradeNo(outTradeNo);
              refundRequest.setOutRefundNo(outRefundNo);
              refundRequest.setTotalFee(depositMap.get(applyInfo.getPromotionId()).intValue());
              refundRequest.setRefundFee(depositMap.get(applyInfo.getPromotionId()).intValue());
              WxPayRefundResult refundResult = payService.refund(refundRequest);
              log.info("refundResult:"+JSON.toJSONString(refundResult));
            }catch(Exception e){
              log.error("refund fail:"+e.getMessage(),e);
            }
          }
        }
      }
    }

    //到了活动结束时间，但是还没有改状态的
    QueryWrapper<Promotion> activityEndWrapper = new QueryWrapper<>();
    activityEndWrapper.eq("is_delete", 0);
    activityEndWrapper.in("status", PromotionStatusEnum.APPLY_NOT_STATED.getCode(),
        PromotionStatusEnum.APPLY_ING.getCode(), PromotionStatusEnum.ACTIVITY_NOT_STATED.getCode(),
        PromotionStatusEnum.ACTIVITY_ING.getCode());
    activityEndWrapper.lt("activity_end_time", LocalDateTime.now());
    List<Promotion> promotionEndList = baseMapper.selectList(activityEndWrapper);
    List<Long> promotionEndIdList = promotionEndList.stream().map(Promotion::getId)
        .collect(Collectors.toList());
    if (promotionEndIdList != null && promotionEndIdList.size() > 0) {
      endPromotion(promotionEndIdList,null);
    }

    return R.success();
  }

  @Override
  public R updatePromotion(Promotion promotionEntity) {
    baseMapper.updateById(promotionEntity);
    return R.success();
  }

  @Override
  public void endPromotion(List<Long> promotionEndIdList, LocalDateTime endTime){
    Promotion updatePromotion = new Promotion();
    updatePromotion.setStatus(PromotionStatusEnum.ACTIVITY_CLOSED.getCode());
    if(endTime!=null){
      updatePromotion.setActivityEndTime(endTime);
    }
    baseMapper
            .update(updatePromotion, Wraps.<Promotion>lbQ().in(Promotion::getId, promotionEndIdList));

    QueryWrapper<PromotionApplyInfo> applyInfoQueryWrapper = new QueryWrapper<>();
    applyInfoQueryWrapper.in("promotion_id", promotionEndIdList);
    applyInfoQueryWrapper.eq("status", PromotionAuditStatusEnum.APPLY_SUCCESS.getCode());
    applyInfoQueryWrapper.eq("is_delete", 0);
    List<PromotionApplyInfo> promotionApplyInfoList = promotionApplyInfoMapper
            .selectList(applyInfoQueryWrapper);
    if (promotionApplyInfoList.size() > 0) {
      Map<String, Set<Long>> productIdPromotionIdSetMap = new HashMap<>();
      for (PromotionApplyInfo applyInfo : promotionApplyInfoList) {
        String[] productIds = applyInfo.getProductIds().split(",");
        for (String productId : productIds) {
          if (productIdPromotionIdSetMap.get(productId) == null) {
            productIdPromotionIdSetMap.put(productId, new HashSet<Long>());
          }
          productIdPromotionIdSetMap.get(productId).add(applyInfo.getPromotionId());
        }
      }
      for (Map.Entry<String, Set<Long>> entry : productIdPromotionIdSetMap.entrySet()) {
        String key = PromotionConstant.PRODUCT_PROMOTION_RELATION_PREFIX + entry.getKey();
        Set<Long> promotionIdSet = entry.getValue();
        for (Long promotionId : promotionIdSet) {
          redisRepository.getRedisTemplate().opsForZSet().remove(key, promotionId.toString());
        }
      }
    }

    //将活动信息从缓存中清除掉
    for (Long promotionId : promotionEndIdList) {
      redisRepository.del(PromotionConstant.PROMOTION_PREFIX + promotionId);
    }

    //删除活动和商品的关联关系
    promotionProductMapper.deleteRelation(promotionEndIdList);
  }

}
